from engine import Engine,Task,ThreadedTask,Runner
import pygame,textwrap

def combine(a,b,c):return [c(a[i],b[i]) for i in range(0,len(a))]

def trykey(a,b,c):
	try:return a[b]
	except KeyError:return c

def sidechain(f,o,*args,**kwargs):
	f(o,*args,**kwargs)
	return o

def transparent(surf, colorkey=[0,0]):
	surf.set_alpha(None)
	surf.set_colorkey(surf.get_at(colorkey)[:3] if len(colorkey)== 2 else colorkey[:3])

def flatten(l):return [item for sublist in l for item in sublist]

load_trans_image = lambda fileobj: sidechain(transparent,pygame.image.load(fileobj))

load_font = lambda fileobj: Font(pygame.image.load(fileobj))

class Font:

	def __init__(self, surf,**kw): # rename src to surf
		w,h=trykey(kw,'wide',16),trykey(kw,'high',16)
		assert w*h==256
		self.src = sidechain( transparent, surf)
		self.char_width = self.src.get_width()//w
		self.char_height = self.src.get_height()//h
		# Ensure a consistent char-width/height.
		assert surf.get_width()%self.char_width==0 and surf.get_height()%self.char_height==0

	def trim_width(self,w):return w-(w%self.char_width)

	def render_lines(self,width,*lines):
		assert width%self.char_width==0
		width//=self.char_width
		def render_section(line):
			def render_surface(text):
				pos=[0,0]
				s=pygame.Surface((len(text)*self.char_width,self.char_height))
				s.fill((255,0,255))
				for c in text:
					num_code=ord(c)
					src_pos=[0,0,1,1]
					src_pos[0]=num_code%16
					src_pos[1]=(num_code-src_pos[0])//16
					src_pos=[src_pos[0]*self.char_width,src_pos[1]*self.char_height]
					src_pos+=[self.char_width,self.char_height]
					s.blit(self.src,pos,src_pos)
					pos[0]+=self.char_width
				return sidechain(transparent,s,(255,0,255))
			return [render_surface(l) for l in textwrap.wrap(line,width)]
		lines=flatten([line.split('\n') for line in lines])
		return flatten([render_section(section) for section in lines])

	def render(self, width, text):
		assert width%self.char_width == 0
		rows=self.render_lines(width,*text.split('\n'))
		ret = pygame.Surface((width*self.char_width, len(rows)*self.char_height))
		ret.fill((255,0,255))
		pos = [0,0]
		for row in rows:
			ret.blit(row,pos)
			pos[1] += row.get_height()
		return sidechain(transparent,ret,(255,0,255))

class AnimatedMap:

	def __init__(self,rate,m,accelerate_rate=1):
		self.steps,self.pos=m,0
		self.timer=Period(rate)
		self.accelerate,self.accelerate_rate=False,accelerate_rate

	def reset(self):self.pos=0

	@property
	def complete(self):return self.pos>=len(self.steps)

	@property
	def value(self):
		if self.timer.elapsed:
			self.pos+=self.accelerate_rate if self.accelerate else 1
			self.accelerate=False
		return self.steps[-1] if self.complete else self.steps[self.pos]

def AnimationFromGenerator(rate,r):
	return AnimatedMap(rate,[frame for frame in r])

class Period:
	def __init__(self,rate,start=False):
		self.timer=pygame.time.Clock()
		self._elapsed_,self.rate=rate if start else 0,rate
	def reset(self):
		self._elapsed_=0
		self.timer.tick()
	def elapse(self):
		self._elapsed_=self.rate
		self.timer.tick()
	@property
	def elapsed(self):
		self._elapsed_+=self.timer.tick()
		if self._elapsed_>=self.rate:
			self._elapsed_=0
			return True
		return False

class FrameLimiter:
	def __init__(self,rate):
		self.clock=pygame.time.Clock()
		self.rate=rate
	def __call__(self):return self.clock.tick(self.rate)

class SDLEngine(Engine):

	def __init__(self,name=None,resolution=(640,480),frame_rate=30):
		Engine.__init__(self)
		self.resolution,self.surf=resolution,None
		self.name=name or type(self).__name__
		self.connect_signal('frame',pygame.display.flip,FrameLimiter(frame_rate),self.broadcast_events)
		self.connect_signal('start',self.prepare_screen)
		self.connect_signal(pygame.QUIT,self.quit_event)
		self.connect_signal('stop',pygame.quit)

	def quit_event(self,event):self.stop()

	def prepare_screen(self):
		pygame.display.init()
		self.surf=pygame.display.set_mode(self.resolution)
		pygame.display.set_caption(self.name)

	def broadcast_events(self):
		for event in pygame.event.get():
			self.process_signal(event.type,event)
			for task in self.tasks[:]:
				task.process_signal(event.type,event)

if __name__=="__main__":

	class PaintScreen(Task):

		def __init__(self,engine):
			Task.__init__(self,engine)
			self.connect_signal('frame',self.paint)
			self.color_timer=Period(10)
			self._color_=[0]*3

		@property
		def color(self):
			if self.color_timer.elapsed:
				def wrap(n,m):return n if n<=m else 0
				self._color_=[wrap(self._color_[0]+1,255)]*3
			return self._color_

		def set_delegates(self):self.delegates=[self.paint]

		def paint(self):self.engine.surf.fill(self.color)

	class SDLDemo(SDLEngine):

		def __init__(self):
			SDLEngine.__init__(self)
			self.connect_signal('start',PaintScreen(self).start)

	Runner(SDLDemo())()